package com.tqz.zookeeper.rawapi;

import com.tqz.zookeeper.common.ZookeeperUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.AddWatchMode;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

import java.util.concurrent.TimeUnit;

/**
 * zk客户端测试类.
 *
 * @author <a href="https://github.com/tian-qingzhao">tianqingzhao</a>
 * @since 2025/9/4 16:35
 */
@Slf4j
public class ZookeeperClientTest {

    public static void main(String[] args) throws InterruptedException, KeeperException {
        ZooKeeper zooKeeper = ZookeeperUtil.create();

        String path = "/test";

        Stat existsStat = zooKeeper.exists(path,
                event -> log.info("监听：{} 节点是否存在的类型：{}，状态：{}", path, event.getType(), event.getState()));
        if (existsStat == null) {
            // 1.持久节点（persistent node)：一直存在，一直存储在ZooKeeper 服务器上，
            // 即使创建该节点的客户端与服务端的会话关闭了，该节点依然不会被删。 create /locks
            // 2.临时节点 (ephemeral node)：当创建该临时节点的客户端会话因超时或发生异常而关闭时，
            // 该节点也相应在 ZooKeeper 服务器上被删除。 create -e /locks/DBLock
            // 3.有序节点 (sequential node)：并不算是一种单独种类的节点，而是在持久节点和临时节点特性的基础上，增加了一个节点有序的性质。
            // 在我们创建有序节点的时候会自动使用一个单调递增的数字作为后缀。 create -e -s /jobs/job(有序临时节点)
            // 4.容器节点 (container node)：当一个容器节点的最后一个子节点被删除后，容器节点也会被删除。 create -c /work
            // 5.TTL节点 (ttl node)：当一个TTL节点在 TTL 内没有被修改并且没有子节点，会被删除。
            // 注意：默认此功能不开启，需要修改配置文件extendedTypesEnabled=true  create -t 3000 /ttl_node

            // CreateMode有以下几种类型：
            // 1.持久节点(PERSISTENT): 这样的znode在创建之后即使发生ZooKeeper集群宕机或者client宕机也不会丢失。
            // 2.临时节点(EPHEMERAL ): client宕机或者client在指定的timeout时间内没有给ZooKeeper集群发消息，这样的znode就会消失
            // 3.持久顺序节点(PERSISTENT_SEQUENTIAL): znode除了具备持久性znode的特点之外，znode的名字具备顺序性。
            // 4.临时顺序节点(EPHEMERAL_SEQUENTIAL): znode除了具备临时性znode的特点之外，zorde的名字具备顺序性。
            // zookeeper主要用到的是以上4种节点。
            // 5.Container节点 (3.5.3版本新增)：Container容器节点，当容器中没有任何子节点，该容器节点
            // 会被zk定期删除（定时任务默认60s 检查一次)。 和持久节点的区别是 ZK 服务端启动后，会有一个单
            // 独的线程去扫描，所有的容器节点，当发现容器节点的子节点数量为 0 时，会自动删除该节点。可以
            // 用于 leader 或者锁的场景中。
            // 6.TTL节点: 带过期时间节点，默认禁用，需要在zoo.cfg中添加 extendedTypesEnabled=true 开启。
            // 注意：TTL不能用于临时节点个节点有序的性质。

            String createResult = zooKeeper.create(path, "abc666".getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            log.info("createResult：{}", createResult);
        }

        // 3.6.0版本新增永久性watch
        // addWatch [-m mode] path
        // addWatch的作用是针对指定节点添加事件监听，支持两种模式
        // PERSISTENT，持久化订阅，针对当前节点的修改和删除事件，以及当前节点的子节点的删除和新增事件。
        // PERSISTENT_RECURSIVE，持久化递归订阅(默认)，在PERSISTENT的基础上，
        // 增加了子节点修改的事件触发，以及子节点的子节点的数据变化都会触发相关事件（满足递归订阅特性）
        zooKeeper.addWatch(path,
                event -> log.info("监听：{} 节点的变化的数据事件：{}", path, event),
                AddWatchMode.PERSISTENT_RECURSIVE);

        Stat listenStat = new Stat();
        byte[] data = zooKeeper.getData(path,
                event -> log.info("获取数据的监听器：{}", event),
                listenStat);
        log.info("获取到的数据：{}", new String(data));

        int version = listenStat.getVersion();
        // 根据version字段更新数据，类似于乐观锁，可以防止ABA问题
        Stat setDataStat = zooKeeper.setData(path, "abc888".getBytes(), version);
        log.info("重新设置数据获取的 stat：[{}]", ZookeeperUtil.formatStat(setDataStat));

        TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
    }
}
